/*
 * Copyright 2007-2008 Michele Mostarda ( michele.mostarda@gmail.com ).
 * All Rights Reserved.
 * <p/>
 * Licensed under the Apache License, Version 2.0 (the 'License');
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p/>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p/>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an 'AS IS' BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */


package org.michelemostarda;

import java.io.PrintStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;

/**
 * Emits words and tokens generated by the {@link Converter}
 * on a given {@link java.io.PrintStream}.
 */
public class Emitter {

    /**
     * Emitter buffer, used when <i>bufferize</i> is <code>true</i>.
     */
    private List<Token> tokens;

    /**
     * If <code>true</code>the buffer is enabled.
     */
    private boolean bufferize = false;

    /**
     * If <code>true</code> every string is HTML encoded.
     */
    private boolean encoding = false;

    /**
     * If <code>true</code> a space is added at the end of every emitted
     * word, otherwise none.
     */
    private boolean addSpace = false;


    /**
     * The emitter output stream.
     */
    private PrintStream printStream;

    /**
     * Constructor.
     *
     * @param ps the print stream used to generate output code.
     */
    Emitter(PrintStream ps) {
        if(ps == null) {
            throw new IllegalArgumentException();
        }

        printStream = ps;
        tokens = new ArrayList<Token>();
    }

    /**
     * Constructor.
     */
    Emitter() {
        this(System.out);
    }

    /**
     * Allows to change the print stream of the emitter.
     *
     * @param os Allows to se the output stream.
     */
    public void setOutputStream(OutputStream os) {
        reset();
        if( os instanceof PrintStream) {
            printStream = (PrintStream) os;
        } else {
            printStream = new PrintStream(os);
        }
    }

    /**
     * Enables / disables buffering.
     *
     * @param b
     */
    void setBufferize(boolean b) {
        bufferize = b;
        if (!b) {
            flush();
        }
    }

    /**
     * Enables / disables encoding.
     * @param b
     */
    void setEncoding(boolean b) {
        encoding = b;
    }

    /**
     * Sets the <i>addSpace</i> flag.
     * @param b
     */
    void setAddSpace(boolean b) {
        addSpace = b;
    }

    /**
     * Removes from the buffer the <i>i-th</i> element.
     * @param index
     * @return
     */
    Token removeElem(int index) {
        try {
            return tokens.remove(index);
        } catch (IndexOutOfBoundsException ioe) {
            return null;
        }
    }

    /**
     * Flushes the content of the buffer on the output stream.
     */
    void flush() {
        for (Token wt : tokens) {
            wt.emitOn(this);
        }
        tokens.clear();
    }

    /**
     * Resets the emitter internal status.
     */
    void reset() {
        flush();
        setBufferize(false);
        setEncoding(false);
        setAddSpace(false);
    }

    /**
     * Emits a string on the output stream, buffering it first if <i>bufferize</i>
     * is enabled.
     *
     * @param s
     */
     void emit(String s) {
        String newS = encoding ? encodeHTML(s) : s;
        if (bufferize) {
            tokens.add(tokens.size(), new WordToken(newS));
        } else {
            print(newS);
        }
    }

    /**
     * Prints a string on the output stream.
     * 
     * @param s
     */
    void print(String s) {
        printStream.print(s);
        if(addSpace) { printStream.print(" "); }
    }

    /**
     * Provides an encoding for HTML code embedded in preformatted blocks.
     * 
     * @param s
     * @return
     */
    static String encodeHTML(String s) {
        StringBuffer out = new StringBuffer();
        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);
            if (c > 127 || c == '"' || c == '<' || c == '>') {
                out.append("&#" + (int) c + ";");
            } else {
                out.append(c);
            }
        }
        return out.toString();
    }
}

